/*
 * fat32.c
 *
 *  Created on: 23/09/2011
 *      Author: alejandro
 */

#include "direccionamiento.h"
#include <string.h>
#include <stdint.h>
#include <math.h>
#include <stdlib.h>
#include "queue.h"
#include "fat32.h"
#include "utils.h"

#define sectPorCache 16
#define entradasPorSector 128
#define tamanioFAT 8192

extern Boot_Sector BootSector;
extern Fat FAT;
extern sem_t fatSemaforo;

Boot_Sector fat32_LeerBootSector() {
	Boot_Sector Boot;
	char *buffer;
	buffer = mandarALeerBloque(0);
	memcpy(&Boot, buffer, 512);
	free(buffer);
	return Boot;
}

//Cambiamos a bloques fijos
void fat32_EscribirFatRegion() {
	uint32_t offset;
	offset = (BootSector.rsvdSecCnt + ((FAT.offset*4) / 512)) / 2;
	int i, j = 0;
	for (i = offset; i < offset + (sectPorCache) / 2; i++) {
		mandarAEscribirBloque(i, (char*) (FAT.tabla + j));
		j += entradasPorSector * 2;
	}
	FAT.bitDeUso = noModificado;
}

//Cambiamos a bloques fijos
void fat32_LeerFatRegion(uint32_t posicion) { //16 sectores, 8k traigo a memoria
	char* bloque;
	int i, offset, j = 0;
	if (FAT.bitDeUso==modificado)
		fat32_EscribirFatRegion();
	offset = posicion / (tamanioFAT/4);
	FAT.offset = offset * entradasPorSector * sectPorCache;
	for (i = (BootSector.rsvdSecCnt + offset * sectPorCache)/2; i
			< (BootSector.rsvdSecCnt + offset * sectPorCache + sectPorCache)/2; i++) {
		bloque = mandarALeerBloque(i);
		memcpy(FAT.tabla + (j * 256), bloque, 1024); // 1024/4=256
		j++;
		free(bloque);
	}
	if ((offset + 1) * tamanioFAT > BootSector.secPerFat * 512)
		FAT.maxIndice = ((BootSector.secPerFat * 512 - offset * tamanioFAT) / 4)-1;
	else
		FAT.maxIndice = tamanioFAT/4 -1;
}

int fat32_SeEncuentraEnTabla(uint32_t posicion) {
	return (FAT.offset <= posicion && (FAT.offset + tamanioFAT/4 -1) >= posicion);
}

uint32_t fat32_SiguienteCadenaFat(uint32_t posicion) {
	uint32_t nuevaPos;

	if (fat32_SeEncuentraEnTabla(posicion))
		nuevaPos = FAT.tabla[posicion - FAT.offset];
	else {
		fat32_LeerFatRegion(posicion);
		nuevaPos = FAT.tabla[posicion - FAT.offset];
	}
	if(fat32_esFinDeCadena(posicion))
		perror("Fin de cadena");
	return nuevaPos;
}

int fat32_esFinDeCadena(uint32_t posicion) {
	return (posicion >= 0x0FFFFFF8 && posicion <= 0x0FFFFFFF);
}

uint32_t fat32_ObtenerClustersLibres() {
	uint32_t i, j, libres = 0;
	for (j = 0; j < (BootSector.secPerFat * 512) / tamanioFAT; j++) { // 512/4
		fat32_LeerFatRegion(j * (tamanioFAT/4));
		for (i = 0; i <= FAT.maxIndice; i++) {
			if (FAT.tabla[i] == 0)
				libres++;
		}
	}
	return libres;
}

uint32_t fat32_obtenerNumeroDeBloque(uint32_t numeroDeCluster){
	return (BootSector.rsvdSecCnt + BootSector.numFats * BootSector.secPerFat
	+ (numeroDeCluster - 2) * BootSector.secPerClus)/2;
}

t_queue* fat32_ObtenerNClustersLibres(int n) {
	t_queue* clustersLibres = collection_queue_create();
	int i, j, libres = 0;
	for (j = 0; j < ((BootSector.secPerFat * 512) / tamanioFAT) && libres < n; j++) {
		fat32_LeerFatRegion(j * (tamanioFAT/4));
		for (i = 0; libres < n && i <= FAT.maxIndice; i++)
			if (FAT.tabla[i] == 0) {
				libres++;
				collection_queue_push(clustersLibres, (void*) (i + FAT.offset));
			}
	}
	if (n < libres){
		printf("espacio insuficiente");
		exit(1);
	}
	return clustersLibres;
}

void fat32_AgregarACadenaDeClusters(int cantClusters, int posicionFAT) {
	t_queue* clustersLibres;
	int cluster;


	if (!fat32_SeEncuentraEnTabla(posicionFAT))
		fat32_LeerFatRegion(posicionFAT);

	while (1 /*!fat32_esFinDeCadena(FAT.tabla[posicionFAT - FAT.offset])*/){ //TODO ARREGLAR
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		if (!fat32_esFinDeCadena(posicionFAT))
			break;
		if (!fat32_SeEncuentraEnTabla(posicionFAT))
			fat32_LeerFatRegion(posicionFAT);
	}
	clustersLibres = fat32_ObtenerNClustersLibres(cantClusters);

	while (cantClusters > 0) {
		cluster = (int) collection_queue_pop(clustersLibres);
		if(!fat32_SeEncuentraEnTabla(posicionFAT))
			fat32_LeerFatRegion(posicionFAT);
		FAT.tabla[posicionFAT - FAT.offset] = cluster;
		FAT.bitDeUso = modificado;
		posicionFAT = cluster;
		if (fat32_SeEncuentraEnTabla(posicionFAT)) {
			FAT.tabla[posicionFAT - FAT.offset] = 0x0FFFFFFF;
			FAT.bitDeUso = modificado;
		} else {
			fat32_LeerFatRegion(posicionFAT);
			FAT.tabla[posicionFAT - FAT.offset] = 0x0FFFFFFF;
			FAT.bitDeUso = modificado;
		}
		cantClusters--;
	}
	collection_queue_destroy(clustersLibres, NULL);
}

void fat32_agregarPrimerCluster(Dir *Cluster, infoDir *info){
	t_queue* clusterLibre;
	uint32_t nroCluster;
	sem_wait(&fatSemaforo);
	clusterLibre = fat32_ObtenerNClustersLibres(1);
	nroCluster = (uint32_t) collection_queue_pop(clusterLibre);
	if(!fat32_SeEncuentraEnTabla(nroCluster))
		fat32_LeerFatRegion(nroCluster);
	FAT.tabla[nroCluster - FAT.offset] = 0x0FFFFFFF;
	FAT.bitDeUso = modificado;
	sem_post(&fatSemaforo);
	char *src = (char *) &nroCluster;
	char *dst = (char *) &(Cluster[info->posicionDirectorio].FstClusLO);
	dst[0] = src[0];
	dst[1] = src[1];
	dst = (char *) &(Cluster[info->posicionDirectorio].FstClusHI);
	dst[0] = src[2];
	dst[1] = src[3];
	info->clusterContenido=nroCluster;
}

void fat32_removerPrimerCluster(Dir *Cluster, infoDir *info){
	sem_wait(&fatSemaforo);
	FAT.tabla[info->clusterContenido - FAT.offset] = 0;
	FAT.bitDeUso = modificado;
	sem_post(&fatSemaforo);
	uint32_t nroCluster = 0;
	char *src = (char *) &nroCluster;
	char *dst = (char *) &(Cluster[info->posicionDirectorio].FstClusLO);
	dst[0] = src[0];
	dst[1] = src[1];
	dst = (char *) &(Cluster[info->posicionDirectorio].FstClusHI);
	dst[0] = src[2];
	dst[1] = src[3];
	info->clusterContenido=0;
}

uint32_t fat32_cantidadDeClusterAgregar(uint32_t nuevoFilesize, uint32_t viejoFilesize){
	uint32_t clustersNecesarios;
	uint32_t clusterExtra =(nuevoFilesize % (BootSector.secPerClus * 512) == 0) ? -1 : 0;
	if (viejoFilesize % (BootSector.secPerClus * 512)==0)
		clusterExtra++;
	clustersNecesarios = (nuevoFilesize / (BootSector.secPerClus * 512)
			- viejoFilesize/(BootSector.secPerClus * 512)) + clusterExtra;
	return clustersNecesarios;

}

/* NO ESTA EN USO
 void fat32_RemoverClusterDeCadena(int cantClusters, int posicionFAT,uint32_t* tablaFAT) {
 pila cadenaClusters = pila_Crear();
 while (!(tablaFAT[posicionFAT]>=0x0FFFFFF8 && tablaFAT[posicionFAT]<=0x0FFFFFFF)) {
 pila_Meter((void*) posicionFAT, cadenaClusters);
 posicionFAT = tablaFAT[posicionFAT];
 }
 while (cantClusters > 0) {
 tablaFAT[posicionFAT] = 0;
 posicionFAT = (int) pila_Sacar(cadenaClusters);
 tablaFAT[posicionFAT] = 0x0FFFFFFFF;
 cantClusters--;
 }
 pila_Destruir(cadenaClusters);
 }*/

Dir* fat32_buscarPosLibreEnDirectorio(uint32_t *posLibre, infoDir info) {
	uint32_t aux;
	Dir *Cluster;
	int encontro = 1, i;
	while (encontro) {
		Cluster = (Dir*) fat32_ObtenerCluster(info.clusterContenido);
		for (i = 0; i <= 128; i++) {
			if (entradaLibre(Cluster[i].Name[0]) && entradaLibre(
					Cluster[i + 1].Name[0])) {
				*posLibre = i;
				return Cluster;
			}
		}
		aux = info.clusterContenido;
		sem_wait(&fatSemaforo);
		info.clusterContenido = fat32_SiguienteCadenaFat(info.clusterContenido);
		if (fat32_esFinDeCadena(info.clusterContenido)) {
			fat32_AgregarACadenaDeClusters(1, aux);
			info.clusterContenido = fat32_SiguienteCadenaFat(aux);
		}
		sem_post(&fatSemaforo);
		free(Cluster);
	}
	return Cluster;
}

int entradaLibre(char fstLetra){
	return (fstLetra==0xE5 || fstLetra==0x00);
}

void fat32_eliminarEntrada(infoDir info){

	Dir* Cluster;
	Cluster = (Dir*) fat32_ObtenerCluster(info.clusterDirectorio);
	if (info.posicionDirectorio == (BootSector.secPerClus * 512) / 32 - 1) { //es la ultima entrada del cluster
		Cluster[info.posicionDirectorio].Name[0] = 0xE5;
		Cluster[info.posicionDirectorio - 1].Name[0] = 0xE5;
	} else {
		if (Cluster[info.posicionDirectorio + 1].Name[0] == 0x00) {
			Cluster[info.posicionDirectorio].Name[0] = 0x00;
			Cluster[info.posicionDirectorio - 1].Name[0] = 0x00;
		} else {
			Cluster[info.posicionDirectorio].Name[0] = 0xE5;
			Cluster[info.posicionDirectorio - 1].Name[0] = 0xE5;
		}
	}
	fat32_mandarAEscribirCluster((char*)Cluster, info.clusterDirectorio);
	free(Cluster);
	if(info.clusterContenido == 0)
		return;
	/*sem_wait(&fatSemaforo);
	if(fat32_SeEncuentraEnTabla(info.clusterContenido))
		FAT.tabla[info.clusterContenido-FAT.offset] = 0;
	else{
		fat32_LeerFatRegion(info.clusterContenido);
		FAT.tabla[info.clusterContenido-FAT.offset] = 0;
	}
	sem_post(&fatSemaforo);*/
}

int fat32_directorioEstaVacio(uint32_t numClus){
	Dir *Cluster;
	int i,terminoDeRecorrer=1;
	while (terminoDeRecorrer) {
		Cluster = (Dir*)fat32_ObtenerCluster(numClus);
		for (i = 0; i < BootSector.secPerClus * 512 / 32; i++) {
			if (Cluster[i].Name[0] == 0x00)
				return 1; //Cluster vacio
			if (Cluster[i].Name[0] != 0xE5)
				return 0; //Cluster no esta vacio
		}
		free(Cluster);
		numClus = fat32_SiguienteCadenaFat(numClus);
		if(fat32_esFinDeCadena(numClus))
			terminoDeRecorrer=0;
	}
	return 1; //Cluster vacio
}

//Cambiamos a bloques fijos
void fat32_mandarAEscribirCluster(char *Cluster, uint32_t numeroDeCluster) {
	int offset, i, j = 0;
	offset = fat32_obtenerNumeroDeBloque(numeroDeCluster);
	for (i = offset; i < offset + (BootSector.secPerClus)/2; i ++) {
		mandarAEscribirBloque(i, Cluster + j);
		j += 1024;
	}
}

void fat32_RemoverClusters(int posicionFAT) {
	int auxPosicion;
	//char* clusterVacio = malloc(BootSector.secPerClus * 512);
	//memset(clusterVacio, 0, BootSector.secPerClus * 512);
	auxPosicion = fat32_SiguienteCadenaFat(posicionFAT);
	FAT.tabla[posicionFAT - FAT.offset] = 0x0FFFFFFF;
	FAT.bitDeUso = modificado;
	posicionFAT = auxPosicion;
	while (!fat32_esFinDeCadena(posicionFAT)) {
		auxPosicion = fat32_SiguienteCadenaFat(posicionFAT);
		//fat32_mandarAEscribirCluster(clusterVacio, posicionFAT);
		FAT.tabla[posicionFAT - FAT.offset] = 0;
		FAT.bitDeUso = modificado;
		posicionFAT = auxPosicion;
	}
	//free(clusterVacio);
}

//Cambiamos a bloques fijos
char* fat32_ObtenerCluster(int numeroDeCluster) {
	char* cluster, *bloque;
	cluster = (char *) malloc(BootSector.secPerClus * 512);
	int offset, i, j;
	offset = fat32_obtenerNumeroDeBloque(numeroDeCluster);
	j = 0;
	for (i = offset; i < offset + (BootSector.secPerClus)/2; i++) {
		bloque = mandarALeerBloque(i);
		memcpy(cluster + j, bloque, 1024);
		j += 1024;
		free(bloque);
	}
	return cluster;
}

char* obtenerNombreCorto(Dir entrada) {
	char*nombreCorto = malloc(13);
	if (entrada.Attr == 0x10) {
		memcpy(nombreCorto, entrada.Name, 8);
		memcpy(nombreCorto + 8, entrada.ext, 3);
		nombreCorto[12] = '\0';
	} else {
		int longitud;
		memcpy(nombreCorto, entrada.Name, 8);
		longitud = strcspn((char*) entrada.Name, " ");
		nombreCorto[longitud] = '.';
		memcpy(nombreCorto + longitud + 1, entrada.ext, 3);
		nombreCorto[longitud + 1 + 3] = '\0';
	}
	return nombreCorto;
}

char* obtenerNombreUTF8(LDir LFN) {
	uint16_t nomb[14];
	char *nameUTF8=malloc(14);
	int i;
	memset(nomb, 0, 26);
	memcpy(nomb, LFN.Name1, 10);
	memcpy(nomb + 5, LFN.Name2, 12);
	memcpy(nomb + 11, LFN.Name3, 4);
	nomb[13]='\0';
	for (i = 0; i < 14; i++) {
		if (nomb[i] == 0x0000)
			break;
	 }
	unicode_utf16_to_utf8_inbuffer(nomb, i, nameUTF8, NULL);
	return nameUTF8;
}

void grabarNombreUTF16(LDir *LFN, char *nombre) {

	uint16_t *nomb =(uint16_t*) malloc(26);

	size_t tamanio = 26;
	/*
	size_t tamanio = 0;
	uint16_t nomb[13];
	uint8_t utf8length = strlen(nombre);
	unicode_utf8_to_utf16_inbuffer(nombre, utf8length, nomb, &tamanio);
	*/
	unicode_utf8_to_utf16_inbuffer(nombre, 13, nomb, &tamanio);

	memcpy(LFN->Name1, nomb, 10);
	memcpy(LFN->Name2, nomb + 5, 12);
	memcpy(LFN->Name3, nomb + 11, 4);
	free(nomb);
	return;
}

char* nombrePath(const char* path, int* cantLetras) {
	char *nombre = malloc(14), i;
	memset(nombre, 0, 14);
	path++;
	for (i = 0; *path != '/' && *path != '\0'; i++) {
		memcpy(nombre + i, path, 1);
		path++;
	}
	*cantLetras = i + 1;
	return nombre;
}

LDir compararEntrada(Dir entrada, LDir LFN, char* nombre, int* encontro,
		int* flagLFN) {
	char* nameEntrada;
	switch (entrada.Attr) {
	case 0x0F: //Long File Name
		memcpy(&LFN, &entrada, 32);
		*flagLFN = 1;
		break;
	case 0x10: //Directorio
		if (!strcmp((char*)entrada.Name, ".") || !strcmp((char*)entrada.Name, "..")) {
			//No Hace nada
		} else {
			if (*flagLFN) {
				nameEntrada = obtenerNombreUTF8(LFN);
				*flagLFN = 0;
			} else {
				nameEntrada = obtenerNombreCorto(entrada);
			}
			if (!strcmp(nameEntrada, nombre))
				*encontro = 1;
			free(nameEntrada);
		}
		break;
	case 0x20: //Archivo
		if (*flagLFN) {
			nameEntrada = obtenerNombreUTF8(LFN);
			*flagLFN = 0;
		} else {
			nameEntrada = obtenerNombreCorto(entrada);
		}
		int a, b;
		a = strlen(nameEntrada);
		b = strlen(nombre);
		if (!strcmp(nameEntrada, nombre))
			*encontro = 1;
		free(nameEntrada);
		break;
	default: //En otro caso
		perror("Atributo no valido");
		break;
	}
	return LFN;
}

/////////////////*************/////////////////
infoDir buscarEnDirectorio(uint32_t numeroCluster, char* nombre) {
	infoDir info;
	Dir* Cluster;
	int i, encontro = 0, flagLFN = 0, fin = 1;
	LDir LFN;

	while (fin) {
		Cluster = (Dir*) fat32_ObtenerCluster(numeroCluster);
		for (i = 0; Cluster[i].Name[0] != 0x00 && i <= BootSector.secPerClus
				* 512 / 32; i++) {
			if (Cluster[i].Name[0] != 0xE5) { //aca habria que poner name[0]!=0x00 tambien
				LFN = compararEntrada(Cluster[i], LFN, nombre, &encontro, &flagLFN);
				if (encontro) {
					char *dst = (char *) &info.clusterContenido;
					char *src = (char *) &(Cluster[i].FstClusLO);
					dst[0] = src[0];
					dst[1] = src[1];
					src = (char *) &(Cluster[i].FstClusHI);
					dst[2] = src[0];
					dst[3] = src[1];
					info.resultadoBusqueda = encontrado;
					info.clusterDirectorio = numeroCluster;
					info.posicionDirectorio = i;
					free(nombre);
					free(Cluster);
					return info;
				}
			}
		}
		if (Cluster[i].Name[0] == 0x00) {
			fin = 0;
			info.resultadoBusqueda = noEncontrado;
		} else {
			sem_wait(&fatSemaforo);
			numeroCluster = fat32_SiguienteCadenaFat(numeroCluster);
			sem_post(&fatSemaforo);
		}
		free(Cluster);
	}
	free(nombre);
	return info;
}

infoDir buscar(const char* path) {
	infoDir info;
	int bandera = 1;	//la bandera es para que la primera vez busque en el /.
	char* nombre;
	int cantLetras;
	if (!strcmp(path, "/")) {
		info.clusterContenido = BootSector.rootClus;
		return info;
	}
	while (*path != '\0') {
		nombre = nombrePath(path, &cantLetras);
		path = path + cantLetras;
		if (bandera) {
			info = buscarEnDirectorio(BootSector.rootClus, nombre);
			bandera = 0;
			if (info.resultadoBusqueda == noEncontrado)
				return info;
		} else {
			info = buscarEnDirectorio(info.clusterContenido, nombre);
			if (info.resultadoBusqueda == noEncontrado)
				return info;
		}
	}
	return info;
}

LDir listarEntradaDirectorio(Dir entrada, LDir LFN, t_queue* colaNombres,
		int *flagLFN) {
	uint8_t* nombre;
	switch (entrada.Attr) {
	case 0x0F: //Long File Name
		memcpy(&LFN, &entrada, 32);
		*flagLFN = 1;
		break;
	case 0x10: //Directorio
		if (!strcmp((char*)entrada.Name, ".") || !strcmp((char*)entrada.Name, "..")) {
			collection_queue_push(colaNombres, (void*) entrada.Name);
		} else {
			if (*flagLFN) {
				nombre = (uint8_t*)obtenerNombreUTF8(LFN);
				*flagLFN = 0;
			} else{
				nombre=(uint8_t*)obtenerNombreCorto(entrada);
			}
			collection_queue_push(colaNombres,(void*) nombre);
		}
		break;
	case 0x20: //Archivo
		if (*flagLFN) {
			nombre = (uint8_t*)obtenerNombreUTF8(LFN);
			*flagLFN = 0;
		} else{
			nombre=(uint8_t*)obtenerNombreCorto(entrada);
		}
		collection_queue_push(colaNombres,(void*)nombre);
		break;
	default: //En otro caso
		perror("entrada erronea");
		break;
	}
	return LFN;
}

void listarDirectorio(uint32_t numeroCluster, t_queue* colaNombres) {
	Dir* EntradasDir;
	int i, flag = 0, fin = 1;
	LDir LFN;
	while (fin) {
		EntradasDir = (Dir*) fat32_ObtenerCluster(numeroCluster);
		for (i = 0; EntradasDir[i].Name[0] != 0x00 && i <= BootSector.secPerClus
				* 512 / 32; i++) {
			if (EntradasDir[i].Name[0] != 0xE5)
				LFN = listarEntradaDirectorio(EntradasDir[i], LFN, colaNombres,
						&flag);
		}
		if (EntradasDir[i].Name[0] == 0x00) {
			fin = 0;
		} else {
			sem_wait(&fatSemaforo);
			numeroCluster = fat32_SiguienteCadenaFat(numeroCluster);
			sem_post(&fatSemaforo);
		}
		free(EntradasDir);
	}
}
